﻿using EnvDTE;
using Microsoft.VisualStudio.Shell;
using RazorEngine.Templating;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using RazorEngine;
using RazorEngine.Configuration;
using EnvDTE80;
using Engine = RazorEngine.Engine;
using static Microsoft.VisualStudio.Shell.RegistrationAttribute;

namespace CodeBuilderForVS2022.Entity
{
    /// <summary>
    /// 帮助类
    /// </summary>
    public class EntityHelper
    {
        public string BaseDic { get; set; }

        public DTE2 DteIns { get; set; }

        public EntityHelper(string baseDic, DTE2 dTE2)
        {
            BaseDic = baseDic;
            DteIns = dTE2;
        }

        #region 代码生成

        /// <summary>
        /// 获取DtoModel
        /// </summary>
        /// <param name="applicationStr"></param>
        /// <param name="name"></param>
        /// <param name="dirName"></param>
        /// <param name="codeClass"></param>
        /// <returns></returns>
        public DtoFileModel GetDtoModel(string applicationStr, string name, string cnName, string description, string dirName, CodeClass codeClass)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            var model = new DtoFileModel() { Namespace = applicationStr, Name = name, CnName = cnName, Description = description, DirName = dirName.Replace("\\", ".") };
            List<ClassProperty> classProperties = new List<ClassProperty>();

            foreach (CodeElement item in codeClass.Bases)
            {
                classProperties.Add(new ClassProperty()
                {
                    Name = "Id",
                    CnName = "主键",
                    PropertyType = item.Name == "BaseGuidEntity" ? "string" : "int"
                }); ;
            }


            var codeMembers = codeClass.Members;
            foreach (CodeElement codeMember in codeMembers)
            {
                if (codeMember.Kind == vsCMElement.vsCMElementProperty)
                {
                    ClassProperty classProperty = new ClassProperty();
                    CodeProperty property = codeMember as CodeProperty;
                    classProperty.Name = property.Name;
                    //获取属性类型
                    var propertyType = property.Type;
                    switch (propertyType.TypeKind)
                    {
                        case vsCMTypeRef.vsCMTypeRefString:
                            classProperty.PropertyType = "string";
                            break;
                        case vsCMTypeRef.vsCMTypeRefInt:
                            classProperty.PropertyType = "int";
                            break;
                        case vsCMTypeRef.vsCMTypeRefBool:
                            classProperty.PropertyType = "bool";
                            break;
                        case vsCMTypeRef.vsCMTypeRefDecimal:
                            classProperty.PropertyType = "decimal";
                            break;
                        case vsCMTypeRef.vsCMTypeRefDouble:
                            classProperty.PropertyType = "double";
                            break;
                        case vsCMTypeRef.vsCMTypeRefFloat:
                            classProperty.PropertyType = "float";
                            break;
                        case vsCMTypeRef.vsCMTypeRefCodeType:
                            classProperty.PropertyType = propertyType.AsFullName;
                            break;
                    }

                    string propertyCnName = property.DocComment.Replace("<doc>", "").Replace("<summary>", "").Replace("</doc>", "").Replace("</summary>", "").Replace("\r\n", "");//属性中文名称

                    classProperty.CnName = string.IsNullOrEmpty(propertyCnName) ? property.Name : propertyCnName;

                    classProperties.Add(classProperty);
                }
            }

            model.ClassPropertys = classProperties;

            return model;
        }

        #region 生成后端

        /// <summary>
        /// 创建Dto类
        /// </summary>
        /// <param name="model"></param>
        /// <param name="name"></param>
        /// <param name="dtoFolder"></param>
        public void CreateInputFile(DtoFileModel model, string name, ProjectItem dtoFolder, LoadingForm loadingForm)
        {
            loadingForm.ShowLoading($"正在生成$Add{name}Input.cs", 2);
            string addInputContent = GetTemplateContent("AddInput", model);
            AddFileToProjectItem(dtoFolder, addInputContent, $"Add{name}Input.cs");

            loadingForm.ShowLoading($"正在生成Update{name}Input.cs", 3);
            string updateInputContent = GetTemplateContent("UpdateInput", model);
            AddFileToProjectItem(dtoFolder, updateInputContent, $"Update{name}Input.cs");

            loadingForm.ShowLoading($"正在生成Get{name}PageResultInput.cs", 4);
            string getPageResultInputContent = GetTemplateContent("GetPageResultInput", model);
            AddFileToProjectItem(dtoFolder, getPageResultInputContent, $"Get{name}PageResultInput.cs");
            loadingForm.ShowLoading($"正在生成Get{name}PageListInput.cs", 5);
            string getPageListInputContent = GetTemplateContent("GetPageListInput", model);
            AddFileToProjectItem(dtoFolder, getPageListInputContent, $"Get{name}PageListInput.cs");
        }

        /// <summary>
        /// 获取模板内容
        /// </summary>
        /// <param name="tempalteName">模板名称如：addInput.cshtml</param>
        /// <param name="model">需要替换的实体</param>
        /// <returns></returns>
        public string GetTemplateContent(string tempalteName, object model)
        {
            string tempalteContent = File.ReadAllText(Path.Combine(BaseDic, "Template", $"{tempalteName}.cshtml"));
            ITemplateKey key = Engine.Razor.GetKey(tempalteName + $"{Guid.NewGuid().ToString().Substring(0, 6)}");
            Engine.Razor.AddTemplate(key, tempalteContent);
            return Engine.Razor.RunCompile(tempalteContent, key, null, model); ;
        }

        /// <summary>
        /// 创建Dto类
        /// </summary>
        /// <param name="model"></param>
        /// <param name="name"></param>
        /// <param name="dtoFolder"></param>
        public void CreateOutputFile(DtoFileModel model, string name, ProjectItem dtoFolder, LoadingForm loadingForm)
        {
            loadingForm.ShowLoading($"正在生成Get{name}Output.cs", 6);
            string queryOutputContent = GetTemplateContent("GetOutput", model);
            AddFileToProjectItem(dtoFolder, queryOutputContent, $"Get{name}Output.cs");

            loadingForm.ShowLoading($"正在生成Get{name}ItemOutput.cs", 7);
            string queryItemOutputContent = GetTemplateContent("GetItemOutput", model);
            AddFileToProjectItem(dtoFolder, queryItemOutputContent, $"Get{name}ItemOutput.cs");

            loadingForm.ShowLoading($"正在生成Get{name}PageResultOutput.cs", 8);
            string getPageResultOutputContent = GetTemplateContent("GetPageResultOutput", model);
            AddFileToProjectItem(dtoFolder, getPageResultOutputContent, $"Get{name}PageResultOutput.cs");

            loadingForm.ShowLoading($"正在生成Get{name}PageListOutput.cs", 9);
            string getPageListOutputContent = GetTemplateContent("GetPageListOutput", model);
            AddFileToProjectItem(dtoFolder, getPageListOutputContent, $"Get{name}PageListOutput.cs");
        }

        /// <summary>
        /// 创建Service类
        /// </summary>
        /// <param name="applicationStr">根命名空间</param>
        /// <param name="name">类名</param>
        /// <param name="dtoFolder">父文件夹</param>
        /// <param name="dirName">类所在文件夹目录</param>
        public void CreateServiceFile(string applicationStr, string name, string cnName, string dirName, LoadingForm loadingForm, CodeClass codeClass)
        {

            var model = new DALFileModel() { Namespace = applicationStr, Name = name, CnName = cnName, DirName = dirName.Replace("\\", ".") };
            #region Prop

            List<ClassProperty> classProperties = new List<ClassProperty>();
            var codeMembers = codeClass.Members;
            foreach (CodeElement codeMember in codeMembers)
            {
                if (codeMember.Kind == vsCMElement.vsCMElementProperty)
                {
                    ClassProperty classProperty = new ClassProperty();
                    CodeProperty property = codeMember as CodeProperty;
                    classProperty.Name = property.Name;
                    //获取属性类型
                    var propertyType = property.Type;
                    switch (propertyType.TypeKind)
                    {
                        case vsCMTypeRef.vsCMTypeRefString:
                            classProperty.PropertyType = "string";
                            break;
                        case vsCMTypeRef.vsCMTypeRefInt:
                            classProperty.PropertyType = "int";
                            break;
                        case vsCMTypeRef.vsCMTypeRefBool:
                            classProperty.PropertyType = "bool";
                            break;
                        case vsCMTypeRef.vsCMTypeRefDecimal:
                            classProperty.PropertyType = "decimal";
                            break;
                        case vsCMTypeRef.vsCMTypeRefDouble:
                            classProperty.PropertyType = "double";
                            break;
                        case vsCMTypeRef.vsCMTypeRefFloat:
                            classProperty.PropertyType = "float";
                            break;
                    }

                    string propertyCnName = property.DocComment.Replace("<doc>", "").Replace("<summary>", "").Replace("</doc>", "").Replace("</summary>", "").Replace("\r\n", "");//属性中文名称

                    classProperty.CnName = string.IsNullOrEmpty(propertyCnName) ? property.Name : propertyCnName;

                    classProperties.Add(classProperty);
                }
            }

            model.ClassPropertys = classProperties;

            CodeElement baseEntity = codeClass.Bases.OfType<CodeElement>().FirstOrDefault();
            if (baseEntity.Name.Contains("Guid"))
            {
                model.IsGuidKey = true;
            }

            #endregion

            //数据访问层接口
            IEnumerable<Project> projects = DteIns.Solution.Projects.OfType<Project>();
            Project projectIDAL = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.IDAL");

            loadingForm.ShowLoading($"正在生成I{name}DAL.cs", 10);
            string content_IDAL = GetTemplateContent("IDAL", model);
            AddFileToProjectItem(projectIDAL.ProjectItems, content_IDAL, $"I{name}DAL.cs");

            //数据访问层实现
            loadingForm.ShowLoading($"正在生成{name}DAL.cs", 11);
            var projectDAL = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.DAL");
            string content_dal = GetTemplateContent("DAL", model);
            AddFileToProjectItem(projectDAL.ProjectItems, content_dal, $"{name}DAL.cs");


            //应用层接口
            Project projectIApplication = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.IApplication");

            loadingForm.ShowLoading($"正在生成I{name}Application.cs", 12);
            string content_IApplication = GetTemplateContent("IApplication", model);
            AddFileToProjectItem(projectIApplication.ProjectItems, content_IApplication, $"I{name}Application.cs");

            //应用层实现
            loadingForm.ShowLoading($"正在生成{name}Application.cs", 13);
            var projectApplication = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.Application");
            string content_Application = GetTemplateContent("Application", model);
            AddFileToProjectItem(projectApplication.ProjectItems, content_Application, $"{name}Application.cs");


            //控制器api
            loadingForm.ShowLoading($"正在生成{name}Controller.cs", 14);
            var projectAPI = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.WebApi");
            string content_api = GetTemplateContent("Controller", model);
            AddFileToProjectItem(projectAPI.ProjectItems.Item("Controllers"), content_api, $"{name}Controller.cs");
        }

        /// <summary>
        /// 编辑CustomDtoMapper.cs,添加映射
        /// </summary>
        /// <param name="applicationProject"></param>
        /// <param name="className"></param>
        /// <param name="classCnName"></param>
        public void SetMapper(Project applicationProject, string className, string classCnName)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            string mapperProfilesSrc = applicationProject.FileName.Substring(0, applicationProject.FileName.LastIndexOf("\\")) + "\\Base\\MapperProfiles.cs";
            ProjectItem customDtoMapperProjectItem = DteIns.Solution.FindProjectItem(mapperProfilesSrc);
            CodeClass codeClass = GetClass(customDtoMapperProjectItem.FileCodeModel.CodeElements);
            var codeChilds = codeClass.Members;
            foreach (CodeElement codeChild in codeChilds)
            {
                if (codeChild.Kind == vsCMElement.vsCMElementFunction && codeChild.Name == "MapperProfiles")
                {
                    var insertCode = codeChild.GetEndPoint(vsCMPart.vsCMPartBody).CreateEditPoint();
                    insertCode.Insert($"            // {classCnName}\r\n");
                    insertCode.Insert($"            CreateMap<{className}, Add{className}Input>().ReverseMap();\r\n");
                    insertCode.Insert($"            CreateMap<{className}, Get{className}Output>().ReverseMap();\r\n");
                    insertCode.Insert($"            CreateMap<{className}, Get{className}PageResultOutput>().ReverseMap();\r\n");
                    insertCode.Insert($"            CreateMap<{className}, Get{className}PageListOutput>().ReverseMap();\r\n");
                    insertCode.Insert($"            CreateMap<{className}, Get{className}ItemOutput>().ReverseMap();\r\n");
                    insertCode.Insert($"            CreateMap<{className}, Update{className}Input>().ReverseMap();\r\n");
                    insertCode.Insert("\r\n");
                }
            }

            customDtoMapperProjectItem.Save();
        }

        /// <summary>
        /// 删除CustomDtoMapper.cs,添加映射
        /// </summary>
        /// <param name="applicationProject"></param>
        /// <param name="className"></param>
        /// <param name="classCnName"></param>
        public void DeleteMapper(Project applicationProject, string className, string classCnName)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            List<string> delList = new List<string>()
            {
                $"CreateMap<{className}, Add{className}Input>().ReverseMap();",
                $"CreateMap<{className}, Update{className}Input>().ReverseMap();",
                $"CreateMap<{className}, Get{className}Output>().ReverseMap();",
                $"CreateMap<{className}, Get{className}ItemOutput>().ReverseMap();",
                $"CreateMap<{className}, Get{className}PageResultOutput>().ReverseMap();",
                $"CreateMap<{className}, Get{className}PageListOutput>().ReverseMap();"
            };

            foreach (string key in delList)
            {
                DteIns.Find.FindReplace(vsFindAction.vsFindActionReplaceAll, key, 256, "", vsFindTarget.vsFindTargetSolution, "", "");
            }
        }

        /// <summary>
        /// 正在编辑GlobalUsing
        /// </summary>
        /// <param name="applicationStr"></param>
        /// <param name="className"></param>
        /// <exception cref="NotImplementedException"></exception>
        public void SetGlobalUsing(string applicationStr, string className)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            IEnumerable<Project> projects = DteIns.Solution.Projects.OfType<Project>();

            //GlobalUsingForDAL
            Project project = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.DAL");
            ProjectItem projectItem = project.ProjectItems.Item("GlobalUsing.cs");
            InsertGlobalUsing(applicationStr, className, projectItem, out CodeElement elements, out EditPoint codeEdit);

            //GlobalUsingForIDAL
            project = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.IDAL");
            projectItem = project.ProjectItems.Item("GlobalUsing.cs");
            InsertGlobalUsing(applicationStr, className, projectItem, out elements, out codeEdit);

            //GlobalUsingForIApplication
            project = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.IApplication");
            projectItem = project.ProjectItems.Item("GlobalUsing.cs");
            InsertGlobalUsing(applicationStr, className, projectItem, out elements, out codeEdit);

            //GlobalUsingForApplication
            project = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.Application");
            projectItem = project.ProjectItems.Item("GlobalUsing.cs");
            InsertGlobalUsing(applicationStr, className, projectItem, out elements, out codeEdit);

            //GlobalUsingForEntity
            project = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.Entity");
            projectItem = project.ProjectItems.Item("GlobalUsing.cs");
            InsertGlobalUsing(applicationStr, className, projectItem, out elements, out codeEdit);

            //GloabalUsingForWebApi
            project = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.WebApi");
            projectItem = project.ProjectItems.Item("GlobalUsing.cs");

            InsertGlobalUsing(applicationStr, className, projectItem, out elements, out codeEdit);
        }

        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="applicationStr"></param>
        /// <param name="className"></param>
        public void DeleteGlobalUsing(string applicationStr, string className)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            IEnumerable<Project> projects = DteIns.Solution.Projects.OfType<Project>();
            Project project = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.DAL");
            ProjectItem projectItem = project.ProjectItems.Item("GlobalUsingForDAL.cs");

            DeleteGlobalUsing(className, applicationStr, projectItem);
            //GlobalUsingForDAL

            //GlobalUsingForEntity

            project = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.Entity");
            ProjectItem projectItemEntity = project.ProjectItems.Item("GlobalUsingForEntity.cs");

            DeleteGlobalUsing(className, applicationStr, projectItemEntity);

            //GlobalUsingForIDAL

            project = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.IDAL");
            ProjectItem projectItemIDAL = project.ProjectItems.Item("GlobalUsingForIDAL.cs");

            DeleteGlobalUsing(className, applicationStr, projectItemIDAL);

            //GloabalUsingForWebApi

            project = projects.FirstOrDefault(x => x.Name == $"{applicationStr}.WebApi");
            ProjectItem projectItemApi = project.ProjectItems.Item("GlobalUsingForWebApi.cs");

            DeleteGlobalUsing(className, applicationStr, projectItemApi);
        }

        public void InsertGlobalUsing(string applicationStr, string className, ProjectItem projectItem, out CodeElement elements, out EditPoint codeEdit)
        {
            elements = projectItem.FileCodeModel.CodeElements.Cast<CodeElement>().LastOrDefault();
            codeEdit = elements.EndPoint.CreateEditPoint();
            codeEdit.Insert("\r\n");
            codeEdit.Insert($"global using {applicationStr}.Entity.Input.{className};\r\n");
            codeEdit.Insert($"global using {applicationStr}.Entity.Output.{className};\r\n");
            projectItem.Save();
        }

        public void DeleteGlobalUsing(string className, string applicationStr, ProjectItem projectItem)
        {
            List<string> delList = new List<string>()
            {
                $"global using {applicationStr}.Entity.Input.{className};",
                $"global using {applicationStr}.Entity.Output.{className};",
            };

            foreach (string key in delList)
            {
                DteIns.Find.FindReplace(vsFindAction.vsFindActionReplaceAll, key, 256, "", vsFindTarget.vsFindTargetSolution, "", "");
            }

        }
        #endregion

        #endregion

        #region 辅助方法

        /// <summary>
        /// 获取Api所在的目录
        /// </summary>
        /// <param name="topProject"></param>
        /// <returns></returns>
        public string GetApiDir(Project topProject)
        {
            var arr = topProject.FileName.Split('\\');
            string projFullName = arr[arr.Length - 2];
            string projName = projFullName.Split('.')[1];
            projFullName = projFullName.Replace(projName, "WebApi");
            var newList = arr.ToList();
            newList.RemoveAt(arr.Length - 1);
            newList.RemoveAt(arr.Length - 2);
            newList.Add(projFullName);
            return string.Join("\\", newList);
        }

        /// <summary>
        /// 获取所有项目
        /// </summary>
        /// <param name="projectItems"></param>
        /// <returns></returns>
        public IEnumerable<ProjectItem> GetProjects(ProjectItems projectItems)
        {
            if (projectItems != null)
            {
                foreach (EnvDTE.ProjectItem item in projectItems)
                {
                    yield return item;

                    if (item.SubProject != null)
                    {
                        foreach (EnvDTE.ProjectItem childItem in GetProjects(item.SubProject.ProjectItems))
                            if (childItem.Kind == EnvDTE.Constants.vsProjectItemKindSolutionItems)
                                yield return childItem;
                    }
                    else
                    {
                        foreach (EnvDTE.ProjectItem childItem in GetProjects(item.ProjectItems))
                            if (childItem != null && childItem.Kind == EnvDTE.Constants.vsProjectItemKindSolutionItems)
                                yield return childItem;
                    }
                }
            }

        }

        /// <summary>
        /// 获取类
        /// </summary>
        /// <param name="codeElements"></param>
        /// <returns></returns>
        public CodeClass GetClass(CodeElements codeElements)
        {
            var elements = codeElements.Cast<CodeElement>().ToList();
            var result = elements.FirstOrDefault(codeElement => codeElement.Kind == vsCMElement.vsCMElementClass) as CodeClass;
            if (result != null)
            {
                return result;
            }
            foreach (var codeElement in elements)
            {
                result = GetClass(codeElement.Children);
                if (result != null)
                {
                    return result;
                }
            }
            return null;
        }

        /// <summary>
        /// 获取当前所选文件去除项目目录后的文件夹结构
        /// </summary>
        /// <param name="selectProjectItem"></param>
        /// <returns></returns>
        public string GetSelectFileDirPath(Project topProject, ProjectItem selectProjectItem)
        {
            string dirPath = "";
            if (selectProjectItem != null)
            {
                //所选文件对应的路径
                string fileNames = selectProjectItem.FileNames[0];
                string selectedFullName = fileNames.Substring(0, fileNames.LastIndexOf('\\'));

                //所选文件所在的项目
                if (topProject != null)
                {
                    //项目目录
                    string projectFullName = topProject.FullName.Substring(0, topProject.FullName.LastIndexOf('\\'));

                    //当前所选文件去除项目目录后的文件夹结构
                    dirPath = selectedFullName.Replace(projectFullName, "");
                }
            }

            return dirPath.Substring(1);
        }

        /// <summary>
        /// 添加文件到项目中
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="content"></param>
        /// <param name="fileName"></param>
        public void AddFileToProjectItem(ProjectItem folder, string content, string fileName)
        {
            string path = Path.GetTempPath();
            Directory.CreateDirectory(path);
            string file = Path.Combine(path, fileName);
            File.WriteAllText(file, content, System.Text.Encoding.UTF8);
            try
            {
                folder.ProjectItems.AddFromFileCopy(file);
            }
            finally
            {
                File.Delete(file);
            }
        }

        /// <summary>
        /// 添加文件到项目中
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="content"></param>
        /// <param name="fileName"></param>
        public void AddFileToProjectItem(ProjectItems folder, string content, string fileName)
        {
            string path = Path.GetTempPath();
            Directory.CreateDirectory(path);
            string file = Path.Combine(path, fileName);
            File.WriteAllText(file, content, System.Text.Encoding.UTF8);
            try
            {
                folder.AddFromFileCopy(file);
            }
            finally
            {
                File.Delete(file);
            }
        }
        #endregion
    }
}
